package hypeerweb;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import communicator.GlobalObjectId;
import communicator.LocalObjectId;
import communicator.ObjectDB;

import states.NodeState;
import states.StandardNodeState;
/** 
 * Description of Node
* 
*
* @author Cameron Smith
* @author Kent Barker
*/
public class Node implements Serializable
{
    
    private WebId webId;
    private Node fold;
    private Node surrogateFold;
    private Node iSurrogateFold;
    
    private Set<Node> neighbors;
    private Set<Node> upPointers;
    private Set<Node> downPointers;
   
    private NodeState state;
    private Contents content = new Contents();
    private GlobalObjectId myId = new GlobalObjectId();
    //cls
    private LocalObjectId key = null;
    public static final Node NULL_NODE = new Node();
    /**
     * creating a Null Node 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    protected Node()
    {
        webId = WebId.NULL_WEB_ID;
        
        fold = null;
        surrogateFold = null;
        iSurrogateFold = null;
        
        neighbors = new HashSet<Node>();
        upPointers = new HashSet<Node>();
        downPointers = new HashSet<Node>();
        
        state = null;
        
    }
    public GlobalObjectId getGlobalId()
    {
        return myId;
    }
    public LocalObjectId get_key()
    {
        return myId.getLocalObjectId();
    }
    public void incrementHeight()
    {
        webId.incrementHeight();
    }
    /**
     * The Normal constructor, creates a node from just a webId and determines height after insertion 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public Node(int id)
    {
        webId = new WebId(id);
        
        fold = this;
        surrogateFold = NULL_NODE;
        iSurrogateFold = NULL_NODE;
        
        neighbors = new HashSet<Node>();
        upPointers = new HashSet<Node>();
        downPointers = new HashSet<Node>();
    
        state = StandardNodeState.getSingleton();
        //System.out.println("NODE(int id) machine address " + myId.getMachineAddr()
         //       + " port " + myId.getPortNumber().toString() + " localId " + myId.getLocalObjectId());
    }
    /**
     * Pretend land constructor -> you have to know height before insertion 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public Node(int id, int height)
    {
        webId = new WebId(id, height);
        
        fold = this;
        surrogateFold = NULL_NODE;
        iSurrogateFold = NULL_NODE;
        
        neighbors = new HashSet<Node>();
        upPointers = new HashSet<Node>();
        downPointers = new HashSet<Node>();
      
        state = null;
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public Node sendMessageTo(WebId toFind)
    {
        //is it me?  if it is, then it's infinite.
        //what happens if the node I'm checking the distance to is NULL_NODE?
        Node nextInPath = NULL_NODE;
        int smallestPath = Integer.MAX_VALUE;
        int temp = 0;
        
        for(Node n : neighbors)
        {
            temp = n.getWebId().distanceTo(toFind);
            if(temp < smallestPath)
            {//check all neighbors to see if their path is smaller that the one I've already found
                smallestPath = temp;
                nextInPath = n;
            }
        }
        for(Node n : upPointers)
        {
            temp = n.getWebId().distanceTo(toFind);
            if(temp < smallestPath)
            {//checking nodes who point to me because the node they want doesn't exist
                smallestPath = temp;
                nextInPath = n;
            }
        } //Slartibartfast!
        for(Node n : downPointers)
        {
            temp = n.getWebId().distanceTo(toFind);
            if(temp < smallestPath)
            {//checking nodes I point to instead of ones that don't exist
                smallestPath = temp;
                nextInPath = n;
            }
        }
        if(fold.getWebId().distanceTo(toFind) < smallestPath)
        {
            nextInPath = fold;
        }
        return nextInPath;
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void addDownPointer(Node downPointer)
    {
        downPointers.add(downPointer);
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void addUpPointer(Node upPointer)
    {
        upPointers.add(upPointer);
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void addNeighbor(Node neighbor)
    {
        neighbors.add(neighbor);
    }
    /**
     * <-- erase this and insert description of function--> 
     * @return 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public SimplifiedNodeDomain constructSimplifiedNodeDomain()
    {
        HashSet<Integer> newNeighbors = NodeSetToIntegerHashSet(neighbors);
        HashSet<Integer> newUpPointers = NodeSetToIntegerHashSet(upPointers);
        HashSet<Integer> newDownPointers = NodeSetToIntegerHashSet(downPointers);
        
        SimplifiedNodeDomain newSNode;
        newSNode = new SimplifiedNodeDomain(webId.getValue(),
                webId.getHeight(),
                newNeighbors,
                newUpPointers, 
                newDownPointers,
                fold.getWebId().getValue(), 
                surrogateFold.getWebId().getValue(),
                iSurrogateFold.getWebId().getValue(),
                state.getId());
        
        return newSNode;
    }  
    public WebId getWebId()
    {
        return webId;
    }
    public NodeState getChildState()
    {
        return state.getChildNodeState();
    }
    public NodeState getParentState()
    {
        return state.getParentNodeState();
    }
    public void removeDownPointer(Node downPointer)
    {
        downPointers.remove(downPointer);
    }
    public void removeUpPointer(Node upPointer)
    {
        upPointers.remove(upPointer);
    }
    public void removeNeighbor(Node neighbor)
    {
        neighbors.remove(neighbor);
    }           
    public void setFold(Node newFold)
    {
        fold = newFold;
    }           
    public void setInverseSurrogateFold(Node newInverseSurrogateFold)
    {
        iSurrogateFold = newInverseSurrogateFold;
    }
    public void setSurrogateFold(Node newSurrogateFold)
    {
        surrogateFold = newSurrogateFold;
    }           
    public void setWebId(WebId newWebId) 
    {
        webId = newWebId;
    }
    public void setState(NodeState newState)
    {
        state = newState;
    }
    public NodeState getState()
    {
        return state;
    }
    /**
     * <-- erase this and insert description of function--> 
     * @return 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public boolean hasNoDownPointers()
    {
        return downPointers.isEmpty();
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * @return 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    private HashSet<Integer> NodeSetToIntegerHashSet(Set<Node> set)
    {
        HashSet<Integer> intSet = new HashSet<Integer>();
        
        for(Node n: set)
        {
            intSet.add(n.getWebId().getValue());
        }

        return intSet;
    }
    /**
     * <-- erase this and insert description of function--> 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void updateState()
    {
        state = state.getNextNodeState(this);//this may have a different name in the nodestate
    }
    public Set<Node> getNeighbors()
    {
        return neighbors;
    }
    /**
     * <-- erase this and insert description of function--> 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void clearUpPointers()
    {
        upPointers.clear();
    }
    public Set<Node> getUpPointers()
    {
        return upPointers;
    }
    public Node getSurrogateFold()
    {
        return surrogateFold;
    }
    public Node getInverseSurrogateFold()
    {
        return iSurrogateFold;
    }
    public Node getFold()
    {
        return fold;
    }
    /**
     * <-- erase this and insert description of function--> 
     * @return 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public Node findCapNode()
    {
        return state.getNextNode(this);//check this line later
    }
    /**
     * <-- erase this and insert description of function--> 
     * @param 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void addToHyPeerWeb(Node node)
    {
        HyPeerWeb.getSingleton().addToHyPeerWeb(node, this);
    }
    public Set<Node> getDownPointers()
    {
        return downPointers;
    }
    /**
     * <-- erase this and insert description of function--> 
     * 
     * @pre <i>None</i>
     * @post <i>None</i>
     */
    public void clearAll()
    {
        fold = NULL_NODE;
        surrogateFold = NULL_NODE;
        iSurrogateFold = NULL_NODE;
        neighbors.clear();
        downPointers.clear();
        upPointers.clear();
        webId = WebId.NULL_WEB_ID;
    }
    /**
     * 
     * 
     * 
     * 
     * @param opNode
     * @param opPoint
     */
    public void replaceDownPointer(Node opNode,Node opPoint){
        downPointers.remove(opNode);
        downPointers.add(opPoint);
    }
    /**
     * 
     * 
     * 
     * 
     * @param opNode
     * @param opPoint
     */
    public void replaceUpPointer(Node opNode,Node opPoint){
        upPointers.remove(opNode);
        upPointers.add(opPoint);
    }
    /**
     * 
     * 
     * 
     * 
     * @param opNode
     * @param opPoint
     */
    public void replaceNeighbor(Node opNode,Node opPoint){
        neighbors.remove(opNode);
        neighbors.add(opPoint);
    }
    public void decrementHeight()
    {
        webId.decrementHeight();
    }
    public Contents getContents()
    {
        return content;
    }
    public void accept(Visitor visitor, Parameters parameters)
    {
        assert(visitor != null);
        assert(parameters != null);
        visitor.visit(this,parameters);
    }
    public Object writeReplace()
    {
        //create a NODEproxy of me. :)
        //return that new nodeproxy
        //only in NODE
        return new NodeProxy(myId);
    }
}